package kryptoBlatt2;


//binaere Matrix
public class BinMatrix {

	int[][] matrix;

	public BinMatrix(int[] matrix){
		int rows = matrix.length;
		this.matrix = new int[rows][1];
		for (int i = 0; i < rows; i++) {
			this.matrix[i][0] = matrix[i];
		}
	}

	public BinMatrix(int[][] matrix) {
		this.matrix = matrix;
	}

	public BinMatrix(int rows, int cols) {
		this.matrix = new int[rows][cols];
	}
	
	//Matrix in entsprechender Schreibweise ausgeben
	public void printMatrix(){
		String acc = "";
		int rows = this.matrix.length;
		int cols = this.matrix[0].length;
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < cols; j++) {
				acc += this.matrix[i][j] + " ";
			}
			acc += "\n";
		}
		System.out.println(acc);
	}
	
	//Modulo Funktion fuer Koerper elemente (hier mod = 2)
	private int mod(int val, int mod){
		int acc = val;
		while(acc < 0){
			acc += mod;
		}
		while(acc >= mod){
			acc -= mod;
		}
		return acc;
	}

	//Matrixmultiplikation
	public BinMatrix matrixMult(BinMatrix m){
		int row = getRowCount();
		int col = m.getColCount();
		int common = getColCount();
		int[][] mM = m.getMatrix();
		int[][] result = new int[row][col];
		int acc = 0;
		for (int i = 0; i < row; i++) {
			for (int j = 0; j < col; j++) {
				acc = 0;
				for (int k = 0; k < common; k++) {
					acc += this.matrix[i][k] * mM[k][j];  
				}
				result[i][j] = mod(acc, 2);
			}
		}
		return new BinMatrix(result);
	}
	
	//Untermatrix durch streichen der i-ten (hier: row) Zeile und j-ten (hier: col) Spalten, fuer Determinantenbestimmung
	private BinMatrix getSubMatrix(BinMatrix m, int row, int col) {
		int oldSize = m.getRowCount();
		int newSize = oldSize - 1;
		int currentRow = 0;
		int currentCol = 0;
		int[][] oldM = m.getMatrix();
		int[][] acc = new int[newSize][newSize];
		for (int i = 0; i < oldSize; i++) {
			for (int j = 0; j < oldSize; j++) {
				if(i != row && j != col){
					acc[currentRow][currentCol] = oldM[i][j];
					currentCol++;
				}
			}
			currentCol = 0;
			if(i != row)
				currentRow++;
		}
		return new BinMatrix(acc);
	}
	
	//Determinante berechnen
	public int determinant(BinMatrix matrix) throws NoSquareException {
	    //Vorbedingung -> Matrix muss quadratisch sein
		if (!matrix.isSquare())
	        throw new NoSquareException("Die Matrix muss quadratisch sein!");
		//1x1 Matrix
	    if (matrix.size() == 1) {
	    	return (int) matrix.getValueAt(0, 0);
	    }
	    //2x2 Matrix
	    if (matrix.size() == 2) {
	        return (int) ((matrix.getValueAt(0, 0) * matrix.getValueAt(1, 1)) - (matrix.getValueAt(0, 1) * matrix.getValueAt(1, 0)));
	    }
	    //Matrizen > 2x2 rekursiv bestimmen
	    int acc = 0;
	    for (int i=0; i < matrix.getColCount(); i++) {
	        acc += changeSign(i) * matrix.getValueAt(0, i) * determinant(getSubMatrix(matrix, 0, i));
	    }
	    return mod(acc, 2);
	} 
	
	//Matrix transponieren
	public BinMatrix transpose(BinMatrix matrix) {
		int rows = matrix.getRowCount();
		int cols = matrix.getColCount();
	    BinMatrix transposedMatrix = new BinMatrix(rows, cols);
	    for (int i = 0; i < rows; i++) {
	        for (int j = 0; j < cols; j++) {
	            transposedMatrix.setValueAt(j, i, matrix.getValueAt(i, j));
	        }
	    }
	    return transposedMatrix;
	} 
	
	//Kofaktormatrix bestimmen, transponiert man diese erhaelt man die Adjunkte (siehe Berechnung Determinante)
	public BinMatrix cofactor(BinMatrix matrix) throws NoSquareException {
		int rows = matrix.getRowCount();
		int cols = matrix.getColCount();
	    BinMatrix mat = new BinMatrix(rows, cols);
	    for (int i = 0; i < rows; i++) {
	        for (int j = 0; j < cols; j++) {
	            mat.setValueAt(i, j, mod(changeSign(i + j) * determinant(getSubMatrix(matrix, i, j)), 2));
	        }
	    }
	    
	    return mat;
	}
		
	//Inverse Matrix bestimmen
	public BinMatrix inverse(BinMatrix matrix) throws NoSquareException, DeterminanteZeroException {
	    return (transpose(cofactor(matrix)).multiplyByInverseDeterminante(getInverse(determinant(matrix), 2)));
	}
	
	//Inverses Element bestimmen, n = Kardinalitaet des Koerpers, x = zu invertierendes Element
	private int getInverse(int x, int n) throws DeterminanteZeroException {
		int acc = 0;
		if(x == 0) throw new DeterminanteZeroException("Die Determinante ist 0!");
		for (int i = 0; i < n; i++) {
			if(i * x == 1) acc = i; 
		}
		return acc;
	}

	//Matrix mit Faktor multiplizieren, hier adjunkte mit dem Inversen Element der Determinante
	private BinMatrix multiplyByInverseDeterminante(int invDet) {
		int rows = getRowCount();
		int cols = getColCount();
		BinMatrix m = new BinMatrix(rows, cols);
		for (int i = 0; i < rows; i++) {
	        for (int j = 0; j < cols; j++) {
	            m.setValueAt(i, j, mod(this.matrix[i][j] * invDet, 2));
	        }
	    }
		return m;
	}
	
	//Signum Funktion
	private int changeSign(int i) {
		int acc = i % 2 == 0 ? 1 : -1;
		return acc;
	}
	
	//Eintrag einer Matrix setzen
	public void setValueAt(int row, int col, int value) {
		this.matrix[row][col] = value;
	}

	//Groesse einer quadratischen Matrix
	private int size(){
		return getRowCount();
	}
	
	//Wert einer Matrix holen
	private int getValueAt(int row, int col){
		return this.matrix[row][col];
	}

	//Abfrage ob Matrix quadratisch ist
	private boolean isSquare() {
		return getColCount() == getRowCount();
	}

	//Alle Werte der Matrix holen
	public int[][] getMatrix(){
		return this.matrix;
	}

	//Anzahl der Zeilen abfragen
	public int getRowCount(){
		return matrix.length;
	}

	//Anzahl der Spalten abfragen
	public int getColCount(){
		return matrix[0].length;
	}

}
